/* ex: set tabstop=4 shiftwidth=4 expandtab: */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <sys/time.h>

#include "rtsp.h" /* for NETAV_PORT */
#include "event.h"

typedef struct {
	int fd;
	event_cb_t cb;
	void *user;
} event_t;

typedef struct timer_ev {
	TAILQ_ENTRY(timer_ev) entries;
	struct timeval timeout;
	timer_cb_t cb;
	void *user;
} timer_ev_t;


/* we won't need more than 4 events, probably */
/* global vars for fd events */
static event_t *events[8] = { NULL };
static int ev_idx = -1;
static int fd_max = -1;
static fd_set read_fds;

/* global vars for timer events */
static TAILQ_HEAD(, timer_ev) timers = TAILQ_HEAD_INITIALIZER(timers);

#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif

static int
listen_init(void)
{
	int listenfd, true = 1;
	struct sockaddr_in local;

	listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (listenfd == -1) {
		perror("socket");
		return -1;
	}

	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(int)) == -1) {
		perror("setsockopt");
		return -1;
	}
	memset(&local, 0, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = htonl(INADDR_ANY);
	local.sin_port = htons(NETAV_PORT);

	if (bind(listenfd, (struct sockaddr *) &local, sizeof(local)) == -1) {
		perror("bind");
		return -1;
	}
	if (listen(listenfd, 5) == -1) {
		perror("listen");
		return -1;
	}

	return listenfd;
}

void
add_event(int fd, event_cb_t cb, void *arg)
{
	event_t *ev = malloc(sizeof(event_t));

	ev->fd = fd;
	ev->cb = cb;
	ev->user = arg;

	events[++ev_idx] = ev;
	FD_SET(fd, &read_fds);
	fd_max = MAX(fd_max, fd) + 1;
}

void
remove_event(int fd)
{
	int i, j;

	for (i = 0; i <= ev_idx; i++) {
		if (fd == events[i]->fd) {
			FD_CLR(fd, &read_fds);
			close(fd);
			free(events[i]);
			events[i] = NULL;
			for (j = i; j < ev_idx; j++)
				events[j] = events[j + 1];
			ev_idx--;
			return;
		}
	}
}


/* timeout is 1/1000000 seconds */
void
sched_event(u_int timeout, timer_cb_t cb, void *arg)
{
	timer_ev_t *ev = malloc(sizeof(timer_ev_t));
	struct timezone tzone;
	suseconds_t usec;

	if (!ev)
		return;

	gettimeofday(&ev->timeout, &tzone);
	usec = ev->timeout.tv_usec + timeout;
	if (usec > 1000000) {
		ev->timeout.tv_sec--;
		usec -= 1000000;
	}
	ev->timeout.tv_usec = usec;
	ev->cb = cb;
	ev->user = arg;
	TAILQ_INSERT_TAIL(&timers, ev, entries);
}


#define timeouted(__t, __now) \
	((__t)->tv_sec < (__now)->tv_sec) ||\
	((__t)->tv_sec == (__now)->tv_sec &&\
	 (__t)->tv_usec <= (__now)->tv_usec)

static void
check_timer(void)
{
	timer_ev_t *ev, *tev;
	struct timeval now;
	struct timezone tzone;

	gettimeofday(&now, &tzone);

	TAILQ_FOREACH_SAFE(ev, &timers, entries, tev) {
		if (timeouted(&ev->timeout, &now)) {
			ev->cb(ev->user);
			TAILQ_REMOVE(&timers, ev, entries);
			free(ev);
		} else
			break;
	}
}

static void
select_loop(int listenfd)
{
	fd_set _read_fds;
	struct sockaddr_in client;
	int r, i;
	struct timeval tv;
	socklen_t addrlen = sizeof(client);


	FD_ZERO(&read_fds);
	FD_SET(listenfd, &read_fds);

	fd_max = listenfd + 1;

	do {
		_read_fds = read_fds;

		tv.tv_sec = 0;
		tv.tv_usec = 1000;

		if ((r = select(fd_max, &_read_fds, NULL, NULL, &tv) == -1)) {
			perror("select");
			exit(-1);
		}

		if (FD_ISSET(listenfd, &_read_fds)) {
			int connfd = accept(listenfd,
					(struct sockaddr *) &client,
				    &addrlen);

			if (connfd != -1) {
				if ((r = open_channel(connfd)) != -1) {
					add_event(connfd, channel_read, NULL);
				}
			}
		}


		for (i = 0; i <= ev_idx; i++) {
			event_t *ev = events[i];
			int fd;

			if (!ev)
				continue;
			fd = ev->fd;
			if (FD_ISSET(fd, &_read_fds)) {
				if (ev->cb(fd, ev->user) == -1)
					remove_event(fd);
			}
		}

		/* timer events */
		check_timer();
	} while (1);
}


void
event_init(void)
{
	int fd;

	fd = listen_init();
	if (fd == -1)
		return;
	printf("NETAV listenning on port %d\n", NETAV_PORT);
	select_loop(fd);
}
